<?php

class FormTree
{

  protected $gateway;

  protected $parent;

  protected $left;

  protected $right;

  protected $table;

  protected $pk;

  public function __construct($gateway, $parent, $left, $right)
  {
    $this->gateway = $gateway;
    $this->parent = $parent;
    $this->left = $left;
    $this->right = $right;
    $this->table = $this->gateway->table;
    $this->pk = $this->gateway->pk;
  }

  public function getParentId()
  {
    return s::request()->get($this->parent, 0);
  }

  public function beforeFindAll(&$params = null)
  {
    $params['where'][] = "$this->parent = :$this->parent";
    $params['data'][$this->parent] = $this->getParentId();
    $params['orderBy'] = $this->left;
  }

  public function beforeInsert(&$data)
  {
    $parent_id = 0;

    if (isset($data[$this->parent])) {
      $parent_id = $data[$this->parent];
    }

    if ($parent_id == 0) {
      $right_id = DB::getInstance()->query()->from($this->table)->select("MAX($this->right)")->execute()->fetchOne();
      $right_id = $right_id + 1;
    }
    else {
      $right_id = DB::getInstance()->query()->from($this->table)->select($this->right)->where("$this->pk = ?")->execute($parent_id)->fetchOne();

      DB::getInstance()->query("UPDATE $this->table SET $this->right = $this->right + 2 WHERE $this->right >= ?")->execute($right_id);
      DB::getInstance()->query("UPDATE $this->table SET $this->left = $this->left + 2 WHERE $this->left >= ?")->execute($right_id);
    }

    $data[$this->parent] = $parent_id;
    $data[$this->left] = $right_id;
    $data[$this->right] = $right_id + 1;
  }

  public function beforeUpdate(&$data)
  {
    $new = $data[$this->parent];

    $row = DB::getInstance()->query()
      ->from($this->table)
      ->select($this->parent)
      ->select($this->left)
      ->select($this->right)
      ->where("$this->pk = ?")
      ->execute($data[$this->pk])
      ->fetchRow();

    $old = $row[$this->parent];

    $id = $data[$this->pk];

    if ($old == $new) {
      return;
    }

    $oldleft = $left = $row[$this->left];
    $oldright = $right = $row[$this->right];

    $branch = DB::getInstance()->query()->select($this->pk)->from($this->table)->where("$this->left BETWEEN $left AND $right")->execute()->fetchCol();
    $branch = implode(', ', $branch);

    /**
     *
     * remove space
     *
     */

    $width = $right - $left + 1;

    DB::getInstance()->query("UPDATE $this->table SET $this->right = $this->right - $width WHERE $this->right > $right")->execute();
    DB::getInstance()->query("UPDATE $this->table SET $this->left = $this->left - $width WHERE $this->left > $right")->execute();

    /**
     *
     * new parent
     *
     */

    $row = DB::getInstance()->query()
      ->from($this->table)
      ->select($this->left)
      ->select($this->right)
      ->where("$this->pk = ?")
      ->execute($data[$this->parent])
      ->fetchRow();

    $left = $row[$this->left];
    $right = $row[$this->right];

    if ($data[$this->parent] == 0) {
      $newleft = DB::getInstance()->query()->from($this->table)->select("MAX($this->right) + 1")->where("$this->pk NOT IN ($branch)")->execute()->fetchOne();
    }

    else {

      /**
       *
       * insert space
       *
       */

      DB::getInstance()->query("UPDATE $this->table SET $this->right = $this->right + $width WHERE $this->right >= $right AND $this->pk NOT IN ($branch)")->execute();
      DB::getInstance()->query("UPDATE $this->table SET $this->left = $this->left + $width WHERE $this->left >= $right AND $this->pk NOT IN ($branch)")->execute();

      $newleft = $right;
    }

    $dist = $newleft - $oldleft;

    /**
     *
     * update subtree
     *
     */

    $sql = "
      UPDATE $this->table
      SET $this->left = $this->left + $dist, $this->right = $this->right + $dist
      WHERE $this->pk IN ($branch)
    ";

    DB::getInstance()->query($sql)->execute();
  }

  public function move($id, $index)
  {
    if (! is_numeric($index) && ! in_array($index, array('top', 'up', 'down', 'bottom', 'first', 'left', 'right', 'last', 'previous', 'next'))) {
      throw new Exception("Invalid index <b>$index</b>");
    }

    $row = DB::getInstance()->query()->from($this->table)->select($this->parent)->select($this->left)->select($this->right)->where("$this->pk = ?")->execute($id)->fetchRow();

    if (empty($row)) {
      throw new Exception("Record not found");
    }

    $parent = $row[$this->parent];
    $oldleft = $row[$this->left];
    $oldright = $row[$this->right];
    $oldwidth = $oldright - $oldleft + 1;

    $branch = DB::getInstance()->query()->select($this->pk)->from($this->table)->where("$this->left BETWEEN $oldleft AND $oldright")->execute()->fetchCol();
    $branch = implode(', ', $branch);

    $q = DB::getInstance()->query()->from($this->table)->select($this->left)->select($this->right)->where("$this->parent = $parent");

    if (is_numeric($index)) {
      $pos = DB::getInstance()
        ->query()
        ->from($this->table)
        ->select("COUNT($this->pk)")
        ->where("$this->parent = $parent")
        ->where("$this->left < $oldleft")
        ->execute()->fetchOne();

      if ($index == $pos) {
        return;
      }

      $data = $q->orderBy($this->left)->offset($index)->limit(1)->execute()->fetchRow();

      if ($index > $pos) {
        $newleft = $oldright + 1;
        $newright = $data[$this->right];

        $width = $newright - $newleft + 1;

        $dir = 1;
      }
      else {
        $newleft = $data[$this->left];
        $newright = $oldleft - 1;

        $width = $oldleft - $newleft;

        $dir = -1;
      }
    }
    else {
      switch ($index) {
        case 'top' :
        case 'first' :
          $data = $q->where("$this->left < $oldleft")->orderBy($this->left)->limit(1)->execute()->fetchRow();

          $newleft = $data[$this->left];
          $newright = $oldleft - 1;

          $width = $oldleft - $newleft;

          $dir = -1;

          break;

        case 'up' :
        case 'left' :
        case 'previous' :
          $data = $q->where("$this->left < $oldleft")->orderBy("$this->left DESC")->limit(1)->execute()->fetchRow();

          $newleft = $data[$this->left];
          $newright = $oldleft - 1;

          $width = $oldleft - $newleft;

          $dir = -1;

          break;

        case 'down' :
        case 'right' :
        case 'next' :
          $data = $q->where("$this->left > $oldleft")->orderBy($this->left)->limit(1)->execute()->fetchRow();

          $newleft = $data[$this->left];
          $newright = $data[$this->right];

          $width = $newright - $newleft + 1;

          $dir = 1;

          break;

        case 'bottom' :
        case 'last' :
          $data = $q->where("$this->left > $oldleft")->orderBy("$this->left DESC")->limit(1)->execute()->fetchRow();

          $newleft = $oldright + 1;
          $newright = $data[$this->right];

          $width = $newright - $newleft + 1;

          $dir = 1;

          break;
      }
    }

    if (! empty($data)) {
      $olddir = - $dir;

      $sql = "
        UPDATE $this->table
        SET $this->left = $this->left + :width, $this->right = $this->right + :width
        WHERE $this->left BETWEEN :left AND :right
      ";

      DB::getInstance()->query($sql)->execute(array('width' => $olddir * $oldwidth, 'left' => $newleft, 'right' => $newright));

      $sql = "
        UPDATE $this->table
        SET $this->left = $this->left + :width, $this->right = $this->right + :width
        WHERE $this->pk IN ($branch)
      ";

      DB::getInstance()->query($sql)->execute(array('width' => $dir * $width));
    }
  }

}
